#!/usr/bin/python
"""
The Ignatz db models.

Copyright 2008 Ryan Barrett
"""

__author__ = 'Ryan Barrett <ignatz at ryanb dot org>'
__url__ = 'http://code.google.com/p/ignatz/'

import datetime
import pickle

from google.appengine.ext import db


class Goal(db.Model):
  """A goal, along with an optional set of milestones.

  The name, description, and due properties are self-explanatory.

  The milestones attribute is a list of Milestone instances. When a Goal is
  persisted, the milestones list is pickled and stored in the _milestones
  property; likewise, when a Goal is fetched from the datastore, the
  _milestones property is unpacked into the milestones list.

  Note that the properties do type validation for us.
  """
  name = db.StringProperty(required=True)
  description = db.StringProperty()
  due = db.DateTimeProperty()
  _milestones = db.BlobProperty()

  def __init__(self, *args, **kwds):
    """Constructor. Overridden to unpickle the milestones."""
    super(Goal, self).__init__(*args, **kwds)

    if '_milestones' in kwds:
      self.milestones = pickle.loads(kwds['_milestones'])
    else:
      self.milestones = []

    self.validate()

  def validate(self):
    """Raises AssertionError if self.milestones isn't a list of Milestones."""
    assert isinstance(self.milestones, list)
    for milestone in self.milestones:
      assert isinstance(milestone, Milestone)

  def put(self):
    """Stores this entity. Overriden to pickle the milestones.

    TODO(ryanb): only re-pickle if the milestones have changed.
    """
    self.validate()
    self._milestones = db.Blob(pickle.dumps(self.milestones))
    return super(Goal, self).put()


class Milestone(object):
  """A sub-component of a goal. Includes a name, description, and due date."""
  name = None
  description = None
  due = None

  def __init__(self, **kwds):
    """Constructor. Attributes may be passed in as keyword arguments."""
    for kwd in kwds:
      setattr(self, kwd, kwds[kwd])

  def __setattr__(self, name, value):
    """Override setattr to do type validation."""
    if name in ('name', 'description'):
      assert isinstance(value, basestring)
    elif name == 'due':
      assert isinstance(value, datetime.datetime)

    super(Milestone, self).__setattr__(name, value)

  def __eq__(self, other):
    return (isinstance(other, Milestone) and
            self.name == other.name and
            self.description == other.description and
            self.due == other.due)
